﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using XNAGameEngine.GameObject;
using XNAGameEngine.Logs;

namespace XNAGameEngine.Collision
{
    #region boundingbox utility
    /// <summary>
    /// This is a static class to reduce the need for creating new bounding box objects to do predictive
    /// collision checks.
    /// </summary>
    public static class StaticBoundingBox
    {
        /// <summary>
        /// This is an object that will be used by this class for calculations
        /// </summary>
        private static BoundingBox _boxToReturn = new BoundingBox(new Vector3(0,0,0), new Vector3(0,0,0));

        /// <summary>
        /// This method is used to calculate the position of a bounding box and then return it for use
        /// in the collision detection loop.  It makes calculations based on the object's current position
        /// as well as its current acceleration and velocity.
        /// </summary>
        /// <param name="objIn">The object whose bounding box is needed; has to have inherited ICollidable</param>
        /// <param name="gameTime">the game time object</param>
        /// <returns>This method returns a bounding box that is updated to the game object's current next position</returns>
        public static BoundingBox GetNewBoundingBox(GameObject2D objIn, GameTime gameTime)
        {
            _boxToReturn.Min.X = objIn._position.X + ((objIn._posVelocity.X + (objIn._posAcceleration.X * gameTime.ElapsedGameTime.Milliseconds)) * gameTime.ElapsedGameTime.Milliseconds);
            _boxToReturn.Min.Y = objIn._position.Y + ((objIn._posVelocity.Y + (objIn._posAcceleration.Y * gameTime.ElapsedGameTime.Milliseconds)) * gameTime.ElapsedGameTime.Milliseconds);

            _boxToReturn.Max.X = _boxToReturn.Min.X + ((ICollidable)objIn).BoundingBox.Max.X;
            _boxToReturn.Max.Y = _boxToReturn.Min.Y + ((ICollidable)objIn).BoundingBox.Max.Y;
            
            return _boxToReturn;
        }
    }
    #endregion

    /// <summary>
    /// This is the collision manager object.  The internal workings of the game engine are the only things
    /// that should call on this object.
    /// </summary>
    public sealed class ManagerCollision : GameComponent, IManagerCollision
    {
        #region constructor

        /// <summary>
        /// Construct a new collision manager
        /// </summary>
        /// <param name="game"></param>
        public ManagerCollision(Game game)
            : base(game)
        {
            Game.Components.Add(this);
            Game.Services.AddService(typeof(IManagerCollision), this);
            ManagerLogs.Write("ManagerCollision : Created");
        }
        #endregion

        #region properties

        /// <summary>
        /// List of objects that are mobile
        /// </summary>
        private readonly List<int> _dynamicObjList = new List<int>();
        
        /// <summary>
        /// List of objects that are stationary
        /// </summary>
        private readonly List<int> _staticObjList = new List<int>();

        #endregion

        #region GameComponent methods

        /// <summary>
        /// This method does all of the collision checks between all of the dynamic objects and each dynamic
        /// object with each static object.  It is doing n^2 checks.  It is currently using predictive AABB collision 
        /// detection.  When a collision is detected both collidable objects are notified of the collision and are
        /// passed the id of the offending object.
        /// </summary>
        /// <param name="gameTime">the game time object</param>
        public override void Update(GameTime gameTime)
        {
            for (int i = 0; i < _dynamicObjList.Count; i++)
            {
                for (int j = i + 1; j < _dynamicObjList.Count; j++)
                {
                    if ((StaticBoundingBox.GetNewBoundingBox(GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_dynamicObjList[i]), gameTime).
                         Contains(StaticBoundingBox.GetNewBoundingBox(GameEngine.GetService<IManagerGameObject2D>().
                                 GetGameObjectById(_dynamicObjList[j]), gameTime))).Equals(ContainmentType.Intersects))
                    {
                        CollisionMessage collisionObjectOne = new CollisionMessage(GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_dynamicObjList[j]));
                        CollisionMessage collisionObjectTwo = new CollisionMessage(GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_dynamicObjList[i]));

                        ((ICollidable)GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_dynamicObjList[i])).Collided(collisionObjectOne);
                        ((ICollidable)GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_dynamicObjList[j])).Collided(collisionObjectTwo);
                    }
                }
                for (int j = 0; j < _staticObjList.Count; j++)
                {
                    if ((StaticBoundingBox.GetNewBoundingBox(GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_dynamicObjList[i]), gameTime).
                       Contains(StaticBoundingBox.GetNewBoundingBox(GameEngine.GetService<IManagerGameObject2D>().
                               GetGameObjectById(_staticObjList[j]), gameTime))).Equals(ContainmentType.Intersects))
                    {
                        CollisionMessage collisionObjectOne = new CollisionMessage(GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_staticObjList[j]));
                        CollisionMessage collisionObjectTwo = new CollisionMessage(GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_dynamicObjList[i]));

                        ((ICollidable)GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_dynamicObjList[i])).Collided(collisionObjectOne);
                        ((ICollidable)GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(_staticObjList[j])).Collided(collisionObjectTwo);
                    }
                }
            }
            base.Update(gameTime);
        }

        #endregion
        
        #region local methods

        public bool RegisterCollidable(int intCollidableId, bool blStatic)
        {
            if (GameEngine.GetService<IManagerGameObject2D>().GetGameObjectIdList().Contains(intCollidableId))
            {
                if (blStatic && !_staticObjList.Contains(intCollidableId) && GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(intCollidableId) is ICollidable)
                {
                    _staticObjList.Add(intCollidableId);
                    return true;
                }
                if (!blStatic && !_dynamicObjList.Contains(intCollidableId) && GameEngine.GetService<IManagerGameObject2D>().GetGameObjectById(intCollidableId) is ICollidable)
                {
                    _dynamicObjList.Add(intCollidableId);
                    return true;
                }
            }
            return false;
        }

        public bool UnRegisterCollidable(int intCollidableId)
        {
            if (_staticObjList.Contains(intCollidableId)) {
                _staticObjList.Remove(intCollidableId);
                return true;
            } 
            if (_dynamicObjList.Contains(intCollidableId)) {
                _dynamicObjList.Remove(intCollidableId);
                return true;
            }
            return false;
        }

        #endregion
    }
}
